package com.flex.reflection;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * @author ganlt
 * @since 2019/09/25 9:37
 */
@Slf4j
public class Reflection {
    private ReflectionBuilder reflectionBuilder;
    /**
     * 扫描包
     */
    private String packagePath;
    /**
     * 扫描类
     */
    private Class<?> packageClasses;

    /**
     * 搜索基础class对象
     */
    private List<Class<?>> classList = new ArrayList<>();

    private List<MetaClass> metaClassList = new ArrayList<>();

    private Reflection() {
    }

    /**
     * 通过路径进行包扫描
     */
    public static ReflectionBuilder packageScan(String packagePath) {
        Reflection reflection = new Reflection();
        reflection.setPackagePath(packagePath);
        return new ReflectionBuilder(reflection);
    }

    /**
     * 通过class进行扫描
     *
     * @param packageClass 扫描类
     */
    public static ReflectionBuilder packageScan(Class<?> packageClass) {
        Reflection reflection = new Reflection();
        reflection.setPackageClasses(packageClass);
        return new ReflectionBuilder(reflection);
    }

    /**
     * 获取所有类
     *
     * @return 根据规则匹配所有类
     */
    public List<MetaClass> getAllClasses() {
        return this.metaClassList;
    }

    /**
     * 筛选出包含注解的类
     *
     * @return 过滤后类列表
     */
    public List<MetaClass> matcherAnnotation(Class<? extends Annotation> annotation) {
        return this.metaClassList.stream().filter((clazz)->clazz.hasAnnotation(annotation)).collect(Collectors.toList());
    }

    /**
     * 筛选出包含接口类
     *
     * @return 过滤后类列表
     */
    public List<MetaClass> matcherInterface(Class<?> interfaces) {
        return this.metaClassList.stream().filter((clazz)->clazz.hasInterface(interfaces)).collect(Collectors.toList());
    }

    /**
     * 返回所有类列表
     */
    private void scan() {
        if (StringUtils.isEmpty(this.packagePath) && Objects.isNull(this.packageClasses)) {
            throw new ReflectionException("package 扫描失败! 请检查是否正确设置packagePath,packageClass");
        }

        if (packageClasses != null) {
            this.packagePath = packageClasses.getPackage().getName();
        }

        packageScanList(packagePath);
        initMetaClass();
    }

    /**
     * 初始化对象信息
     */
    private void initMetaClass() {
        List<Class<?>> classList = this.classList;
        if (classList.isEmpty()) {
            log.warn("未找到任何类");
            return;
        }
        classList.forEach(this::addMetaClass);
    }

    private void addMetaClass(Class<?> clazz) {
        log.debug("扫描增加类:{}", clazz.getName());
        MetaClass metaClass = MetaClass.of(clazz).build();
        metaClassList.add(metaClass);
    }

    /**
     * 根据包名搜索类
     */
    private void packageScanList(String packagePath) {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        String packageDirName = packagePath.replace('.', '/');
        Enumeration<URL> dirs;
        boolean recursive = true;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String protocol = url.getProtocol();
                if ("file".equalsIgnoreCase(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    findAndAddClassesInPackageByFile(packagePath, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    JarFile jar;
                    jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();
                        if (name.charAt(0) == '/') {
                            name = name.substring(1);
                        }
                        if (name.startsWith(packageDirName)) {
                            int idx = name.lastIndexOf('/');
                            if (idx != -1) {
                                packagePath = name.substring(0, idx).replace('/', '.');
                            }
                            if ((idx != -1) || recursive) {
                                if (name.endsWith(".class") && !entry.isDirectory()) {
                                    String className = name.substring(packagePath.length() + 1, name.length() - 6);
                                    try {
                                        classes.add(Class.forName(packagePath + '.' + className));
                                    } catch (ClassNotFoundException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (!classes.isEmpty()) {
                //根据选择过滤
                classes = classes.stream().filter(this::matcherFilter).collect(Collectors.toList());
                this.classList.addAll(classes);
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 根据规则过滤
     *
     * @param clazz 待验证方法
     * @return 是否通过验证
     */
    private boolean matcherFilter(Class<?> clazz) {
        boolean isIncludeInterface = !this.reflectionBuilder.getIncludeInterfaces().isEmpty();
        boolean isExcludeInterface = !this.reflectionBuilder.getExcludeInterfaces().isEmpty();
        boolean isIncludeAnnotaion = !this.reflectionBuilder.getIncludeAnnotations().isEmpty();
        boolean isExcludeAnnotaion = !this.reflectionBuilder.getExcludeAnnotation().isEmpty();


        if (clazz.isEnum()) {
            return false;
        }
        if (this.reflectionBuilder.isExcludeInterface()) {
            if (clazz.isInterface()) {
                return false;
            }
        }
        //规则判断
        return !ruleFilter(clazz, isIncludeInterface, isExcludeInterface, isIncludeAnnotaion, isExcludeAnnotaion);

    }

    private boolean ruleFilter(Class<?> clazz, boolean isIncludeInterface, boolean isExcludeInterface, boolean isIncludeAnnotation, boolean isExcludeAnnotation) {
        //是否过滤掉
        if (isIncludeInterface) {
            boolean isIncludeInterfaceExists = false;
            for (Class inter : this.reflectionBuilder.getIncludeInterfaces()) {
                if (inter.isAssignableFrom(clazz)) {
                    isIncludeInterfaceExists = true;
                }
            }
            if (!isIncludeInterfaceExists) {
                return true;
            }
        }

        if (isExcludeInterface) {
            //排除接口
            boolean isExcludeInterfaceExists = false;
            for (Class inter : this.reflectionBuilder.getExcludeInterfaces()) {
                if (inter.isAssignableFrom(clazz)) {
                    isExcludeInterfaceExists = true;
                }
            }

            if (isExcludeInterfaceExists) {
                return true;
            }

        }

        if (isIncludeAnnotation) {
            Annotation[] annotations = clazz.getAnnotations();
            if (annotations == null) {
                return true;
            } else {
                boolean isIncludeAnnotationExist = false;
                for (Class inter : this.reflectionBuilder.getIncludeAnnotations()) {
                    for (Annotation annotation : annotations) {
                        if (inter.isAssignableFrom(annotation.getClass())) {
                            isIncludeAnnotationExist = true;
                        }
                    }
                }
                if (!isIncludeAnnotationExist) {
                    return true;
                }
            }
        }

        if (isExcludeAnnotation) {
            Annotation[] annotations = clazz.getAnnotations();
            if (annotations != null) {
                boolean isExcludeAnnotationExist = false;
                for (Class inter : this.reflectionBuilder.getExcludeAnnotation()) {
                    for (Annotation annotation : annotations) {
                        if (inter.isAssignableFrom(annotation.getClass())) {
                            isExcludeAnnotationExist = true;
                        }
                    }
                }
                return isExcludeAnnotationExist;
            }
        }
        return false;
    }

    private void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) {
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        File[] dirFiles = dir.listFiles((file) -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
        if (dirFiles == null) {
            return;
        }
        for (File file : dirFiles) {
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(),
                        file.getAbsolutePath(),
                        recursive,
                        classes);
            } else {
                //如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                if (className.contains("$")) {
                    continue;
                }
                try {
                    Class<?> aClass = Class.forName(packageName + '.' + className);
                    classes.add(aClass);
                } catch (ClassNotFoundException e) {
                    log.error("实例化对象失败:{}", packageName + '.' + className);
                }
            }
        }
    }

    private void setReflectionBuilder(ReflectionBuilder reflectionBuilder) {
        this.reflectionBuilder = reflectionBuilder;
    }

    private void setPackagePath(String packagePath) {
        this.packagePath = packagePath;
    }

    private void setPackageClasses(Class<?> packageClasses) {
        this.packageClasses = packageClasses;
    }

    public static class ReflectionBuilder {
        private List<Class<?>> includeInterfaces = new ArrayList<>();
        private List<Class<? extends Annotation>> includeAnnotations = new ArrayList<>();
        private List<Class<?>> excludeInterfaces = new ArrayList<>();
        private List<Class<? extends Annotation>> excludeAnnotation = new ArrayList<>();
        private Reflection reflection;

        private ReflectionBuilder(Reflection reflection) {
            this.reflection = reflection;
        }

        /**
         * 包含接口类
         *
         * @param interfaces 接口类
         * @return 构建类
         */
        public ReflectionBuilder includeInterface(List<Class<?>> interfaces) {
            includeInterfaces.addAll(interfaces);
            return this;
        }

        /**
         * 包含接口
         *
         * @param interfaces 接口类
         * @return 构建类
         */
        public ReflectionBuilder includeInterface(Class<?> interfaces) {
            includeInterfaces.add(interfaces);
            return this;
        }

        /**
         * 包含注解
         *
         * @param annontations 注解类
         * @return 构建类
         */
        public ReflectionBuilder includeAnnotation(List<Class<? extends Annotation>> annontations) {
            includeAnnotations.addAll(annontations);
            return this;
        }

        /**
         * 包含注解
         *
         * @param annontation 注解类
         * @return 构建类
         */
        public ReflectionBuilder includeAnnotation(Class<? extends Annotation> annontation) {
            includeAnnotations.add(annontation);
            return this;
        }

        /**
         * 排除接口
         *
         * @param interfaces 接口类
         * @return 构建类
         */
        public ReflectionBuilder excludeInterface(List<Class<?>> interfaces) {
            excludeInterfaces.addAll(interfaces);
            return this;
        }

        /**
         * 排除接口
         *
         * @param interfaces 接口类
         * @return 构建类
         */
        public ReflectionBuilder excludeInterface(Class<?> interfaces) {
            excludeInterfaces.add(interfaces);
            return this;
        }

        public ReflectionBuilder excludeAnnotation(Class<? extends Annotation> annotationClass) {
            excludeAnnotation.add(annotationClass);
            return this;
        }


        private List<Class<?>> getIncludeInterfaces() {
            return includeInterfaces;
        }

        private List<Class<? extends Annotation>> getIncludeAnnotations() {
            return includeAnnotations;
        }

        private List<Class<?>> getExcludeInterfaces() {
            return excludeInterfaces;
        }

        private List<Class<? extends Annotation>> getExcludeAnnotation() {
            return excludeAnnotation;
        }

        private boolean isExcludeInterface() {
            return true;
        }

        public Reflection build() {
            this.reflection.setReflectionBuilder(this);
            this.reflection.scan();
            return this.reflection;
        }

    }

}
